/* Copyright (C) 2003, Charles Wang.
 * Author:  Charles Wang <charles@linux.net.cn>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANT; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public LIcense for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include  <sys/types.h>
#include  <sys/stat.h>
#include  <sys/mount.h>
#include  <sys/vt.h>
#include  <errno.h>
#include  <fcntl.h>
#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#include  <unistd.h>
#include  <sys/klog.h>           /* for klogctl */
#include  <slang.h>
#include  <newt.h>

#include  "magicbooter.h"
#include  "mblog.h"
#include  "utils.h"
#include  "newtutils.h"

#define   CFG_SAVE_FILE   "/etc/mbsave.py"

/* in spec.c */
int  parse_config_file(const char *filename);

#ifdef USE_UDEV
/* in switchroot.c */
int switch_root(const char *new_root, void (*msgf)(int, const char *, ...), int msgdev);
#endif

static int chvt(int ttyN);
static void save_all(void);
static void parseCmdLineFlags(char *cmdLine);

/* NOTICE:  pre_sysprep and post_sysprep are running in init process,
 *  but the other part are running in magicbooter process. So please
 *  do not assume they share the same process environment.            */
int pre_sysprep(void (*msgf)(int, const char *, ...), int msgdev) {
    /* Remount / in read-write mode. */
    if (mount("rootfs", "/", "rootfs", MS_REMOUNT, NULL) != 0) {
	msgf(msgdev, "Can't remount rootfs.\n");
	return  1;
    }

    if (mount(NULL, "/proc", "proc", MS_MGC_VAL, NULL) != 0) {
	msgf(msgdev, "Can't mount /proc.\n");
	return  1;
    }

    if (mount(NULL, "/sys", "sysfs", MS_MGC_VAL, NULL) != 0) {
	msgf(msgdev, "Can't mount /sys.\n");
	return  1;
    }

    if (mount(NULL, "/proc/bus/usb", "usbfs", MS_MGC_VAL, NULL) != 0) {
	msgf(msgdev, "Can't mount /proc/bus/usb.\n");
	return  1;
    }
    return  0;
}

int post_sysprep(void (*msgf)(int, const char *, ...), int msgdev) {
    struct stat sbuf;
    if (lstat(CFG_SAVE_FILE, &sbuf)) {
        parseCmdLineFlags(NULL);
        if (FLAG_TEST(SHELL)) {
            char *argv[] = {"/bin/sh", NULL};
            msgf(msgdev, "Drop you to a shell, press \"exit\" to reboot.\n");
            exec_and_wait(argv);
        }
	/* No CFG_SAVE_FILE generated, we are going to reboot. */
	msgf(msgdev, "System is going to reboot.\n");
	sleep(2);
            
	return  1;
    }

#ifdef USE_UDEV
    umount("/proc/bus/usb");
    umount("/proc");
    umount("/sys");
    msgf(msgdev, "Switch root into the full run environment: ");
    /* normaly switch_root will not return */
    if (switch_root(mi_initrd_root, msgf, msgdev)) {
        msgf(msgdev, "Switch root failed. System hanged.\n");
        for (;;)
            sleep(1);
        return 1;
    }
#else
    msgf(msgdev, "Pivot root into the full run environment: ");
    if (pivot_root(mi_initrd_root, mi_initrd_root "/mnt/MagicBooter")) {
	msgf(msgdev, "Failed\n");
	msgf(msgdev, "\tpivot_root(%s, %s) -> %s\n",
	     mi_initrd_root, mi_initrd_root "/mnt/MagicBooter",
	     strerror(errno));
	sleep(4);
	return  1;
    }

    umount("/mnt/MagicBooter/proc");
    umount("/mnt/MagicBooter/sys");
#endif
    return  0;
}


/*--- Public variables for mi_initrd. ---*/
const char *mi_initrd_device = "/dev/ram1";
const char *mi_initrd_file = "miinitrd";

/* const char *mi_initrd_cramfs_file = mi_initrd_root "/miinitrd.cramfs"; */

char mi_initrd_fullpath[1024];
char mi_hotfix_fullpath[1024] = "";

char  errbuf[50];

int mb_flags = 0;

#define  SUCCESS   "SUCCESS"
const char * welcome_runner(void);
const char * detect_hardware_runner(void);
const char * loadfloppy_in_cdrom_runner(void);
const char * search_runner(void);
const char * choose_runner(void);
const char * loadre_runner(void);         void loadre_saver(FILE *fp);
#if !defined(NONEWT)
const char * loadscsidrv_runner(void);
#endif
const char * loadfloppy_runner(void);
#if !defined(NONEWT)
const char * search_ether_runner(void);   void search_ether_saver(FILE *fp);
const char * ethercard_runner(void);      void ethercard_saver(FILE *fp);
const char * gateway_dns_runner(void);    void gateway_dns_saver(FILE *fp);
#endif
const char * langset_runner(void);        void langset_saver(FILE *fp);
const char * errquit_runner(void);
const char * reboot_confirm_runner(void);
#ifdef USE_UDEV
const char * start_udev_runner(void);
#endif

mbstep mbstep_arr[] = {
#ifdef USE_UDEV
    { "welcome",        welcome_runner,       "start_udev",   NULL },
    { "start_udev",     start_udev_runner,    "loadfd_in_cd",   NULL },
#else
    { "welcome",        welcome_runner,       "loadfd_in_cd",   NULL },
#endif
    { "loadfd_in_cd",   loadfloppy_in_cdrom_runner, "detect_hardware",   NULL },
    { "detect_hardware", detect_hardware_runner, "search", NULL },
    { "search",         search_runner,        "choose",     NULL },
    { "choose",         choose_runner,        "loadre",     NULL },
    { "loadre",         loadre_runner,        "langset",    loadre_saver },
    { "langset",        langset_runner,        SUCCESS,     langset_saver },
#if !defined(NONEWT)
    { "loadscsidrv",    loadscsidrv_runner,   "choose",     NULL },
    { "search_ether",   search_ether_runner,  "choose",     search_ether_saver },
    { "ethercard",      ethercard_runner,     "choose",     ethercard_saver },
    { "gateway_dns",    gateway_dns_runner,   "choose",     gateway_dns_saver },
    { "loadfloppy",     loadfloppy_runner,    "choose",     NULL },
#endif
    { "errquit",        errquit_runner,        NULL,        NULL },
    { "reboot_confirm", reboot_confirm_runner, NULL,        NULL },
    { NULL, NULL, NULL, NULL }
};

int magicbooter_main(int argc, char *argv[]) {
#if !defined(NONEWT)
    int    out_tty;
    int    scr_width, scr_height;
    int    left;
#endif  /* !NONEWT */

    int    mbs_pos;
    const char *mbs_name, *mbs_next_name;

    char   textbuf[128];

    klogctl(6, NULL, 1);        /* disable kernel printk's to console */
    mblog_open();

    setenv("TERM", "linux", 1);
#if !defined(NONEWT)
    SLang_TT_Read_FD = dup(0); /* newt should read stdin. */
    SLang_TT_Write_FD = dup(1); /* newt should run in stdout. */

    out_tty = open("/dev/tty3", O_WRONLY);
    if (out_tty >= 0) {
	dup2(out_tty, 1);
	dup2(out_tty, 2);
	close(out_tty);
    } else
	mblog("Can't open /dev/tty3 for stdout and stderr: %s.\n", strerror(errno));
#endif

    parseCmdLineFlags(NULL);

    if (!parse_config_file("/etc/mbconfig.conf")) {
        mblog("Can't open /etc/mbconfig.conf file.\n");
        return 1;
    }

    fprintf(stderr, "*** magicbooter started ***\n");

    /* chvt(1); */

#if !defined(NONEWT)
    newtInit();
    newtCls();

    newtGetScreenSize(&scr_width, &scr_height);
#endif

    snprintf(textbuf, sizeof(textbuf), "%s (Powered by MagicInstaller)", distname);

#if !defined(NONEWT)
    left = (scr_width - strlen(textbuf)) / 2;
    if (left < 0)  {
	left = 0;
	textbuf[scr_width] = 0;
    }
    newtDrawRootText(left, 0, textbuf);
    newtPushHelpLine(NULL);
#else                         /* NONEWT */
    printf("%s\n", textbuf);
#endif

    /* This is main step loop. */
    for (mbs_name = "welcome"; mbs_name != NULL; mbs_name = mbs_next_name) {
	if (!strcmp(SUCCESS, mbs_name)) {
	    save_all();
	    break;
	}
	for (mbs_pos = 0; mbstep_arr[mbs_pos].name != NULL; ++mbs_pos)
	    if (!strcmp(mbstep_arr[mbs_pos].name, mbs_name))  break;
	if (mbstep_arr[mbs_pos].name == NULL)  break;
	mblog("STEP: %s\n", mbs_name);
	mbs_next_name = mbstep_arr[mbs_pos].runner();
	if (mbs_next_name == NULL)
	    mbs_next_name = mbstep_arr[mbs_pos].nullnext;
    }

#if !defined(NONEWT)
    newtFinished();
#endif

    fprintf(stderr, "*** magicbooter finished ***\n");

#if !defined(NONEWT)
    dup2(SLang_TT_Write_FD, 1);
    dup2(SLang_TT_Write_FD, 2);
#endif

    mblog_close();

    return  0;
}

static int chvt(int ttyN) {
    int consolefd;

    consolefd = open("/dev/console", O_RDWR);
    if (consolefd < 0) {
	mblog("open(/dev/console, O_RDWR) failed: %s\n", strerror(errno));
	return -1;
    }
    if (ioctl(consolefd, VT_ACTIVATE, ttyN)) {
	mblog("ioctl(consolefd, VT_ACTIVATE, %d) failed: %s\n",
	      ttyN, strerror(errno));
	close(consolefd);
	return -1;
    }
    if (ioctl(consolefd, VT_WAITACTIVE, ttyN)) {
	mblog("ioctl(consolefd, VT_WAITACTIVE, %d) failed: %s\n",
	      ttyN, strerror(errno));
	close(consolefd);
	return -1;
    }
    close(consolefd);
    return 0;
}

static void save_all(void) {
    FILE * fp;
    int    mbs_pos;

    fp = fopen(CFG_SAVE_FILE, "w");
    if (fp == NULL) {
	mblog("fopen('%s', 'w') failed: %s\n", CFG_SAVE_FILE, strerror(errno));
	return;
    }
    fprintf(fp, "#!/usr/bin/python\n");
    for (mbs_pos = 0; mbstep_arr[mbs_pos].name != NULL; ++mbs_pos)
	if (mbstep_arr[mbs_pos].saver != NULL)
	    mbstep_arr[mbs_pos].saver(fp);
    fprintf(fp, "EXPERT_MODE=%d\n", FLAG_TEST(EXPERT));
    fprintf(fp, "RESCUE_MODE=%d\n", FLAG_TEST(RESCUE));
    fclose(fp);
}

const char * welcome_runner(void) {
#if !defined(NONEWT)
    newtWinStatus(42, 5, "Welcome", (char*)welcome);
    sleep(2);
    newtPopWindow();
#else                         /* NONEWT */
    printf("%s\n", welcome);
#endif
    return  NULL;
}

const char * errquit_runner(void) {
    newtWinMessage("Error", "Quit", errbuf);
    return  NULL;
}

const char * reboot_confirm_runner(void) {
    for (;;) {
	switch(newtWinChoice("Confirm", "Reboot", "Back",
			     "Are you sure you want to abort and reboot?")) {
	case 1:
	    return  NULL;
	case 0: case 2:
	    return  "choose";
	}
    }
}

static void parseCmdLineFlags(char *cmdLine) {
    int fd;
    int len;
    char buf[1024];
    char *opt;

    /* if we have any explicit cmdline (probably test mode), we don't want
     * to parse /proc/cmdline */

    if (!cmdLine) {
        if ((fd = open("/proc/cmdline", O_RDONLY)) < 0)
            return;
        len = read(fd, buf, sizeof(buf) - 1);
        close(fd);
        if (len <= 0)
            return;

        buf[len] = '\0';
        cmdLine = buf;
    }

    for (opt = strtok(cmdLine, " \r\n"); opt != NULL; opt = strtok(NULL, " \r\n")) {
        if (!strcasecmp(opt, "expert"))
            FLAG_ENABLE(EXPERT);
        else if (!strcasecmp(opt, "rescue"))
            FLAG_ENABLE(RESCUE);
        else if (!strcasecmp(opt, "shell=1"))
            FLAG_ENABLE(SHELL);
    }
}

void open_shell(void)
{
    if (FLAG_TEST(EXPERT)) {
        if (fork() == 0) {
            int fd;

            mblog("Open /dev/tty4 for shell.\n");
            fd = open("/dev/tty4", O_RDWR);
            if (fd >= 0) {
                dup2(fd, 0);
                dup2(fd, 1);
                dup2(fd, 2);
                close(fd);

                execl("/bin/sh", "/bin/sh", NULL);
                exit(-1);
            }
            else
                mblog("Error opening /dev/tty4 for sh.\n");
        } else
            chvt(4);
    }
}
